
// Copyright (C) 2009-2011 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//  
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//  
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Diagnostics;
using System.Xml;

namespace Derm.Render
{
	/// <summary>
	/// Model matrix implementation.
	/// </summary>
	/// <remarks>
	/// <para>
	/// ModelMatrix class allow to manage a tranformation matrix. It is a 4x4 matrix
	/// of floating point values. These values are able to transform a vertex coordinate.
	/// </para>
	/// </remarks>
	public sealed class ModelMatrix : Matrix4x4
	{
		#region Constructors

		/// <summary>
		/// ModelMatrix constructor.
		/// </summary>
		/// <remarks>
		/// It set this ModelMatrix to identity.
		/// </remarks>
		public ModelMatrix()
		{
			SetIdentity();
		}

		/// <summary>
		/// ModelMatrix copy constructor.
		/// </summary>
		/// <param name="m">
		/// A <see cref="ModelMatrix"/> to be copied.
		/// </param>
		public ModelMatrix(ModelMatrix m) : base(m)
		{

		}

		#endregion

		#region View Matrix Transform Functions

		/// <summary>
		/// The forward vector of this model-view matrix.
		/// </summary>
		public Vertex3f ForwardVector
		{
			get
			{
				Vertex3f forwardVector;

				forwardVector.x = this[0, 2];
				forwardVector.y = this[1, 2];
				forwardVector.z = this[2, 2];

				return (forwardVector);
			}
		}

		#endregion

		#region Model Matrix Transform Functions

		#region Translation

		/// <summary>
		/// Translate this ModelMatrix.
		/// </summary>
		/// <param name="p">
		/// A <see cref="Vertex3f"/> that specifies the translation.
		/// </param>
		public void Translate(Vertex3f p)
		{
			base[3, 0] += p.x;
			base[3, 1] += p.y;
			base[3, 2] += p.z;
		}

		/// <summary>
		/// Translate this ModelMatrix.
		/// </summary>
		/// <param name="p">
		/// A <see cref="Vertex2f"/> that specifies the translation.
		/// </param>
		public void Translate(Vertex2f p)
		{
			base[3, 0] += p.x;
			base[3, 1] += p.y;
		}

		/// <summary>
		/// Translate this ModelMatrix.
		/// </summary>
		/// <param name="x">
		/// A <see cref="float"/> indicating the translation on X axis.
		/// </param>
		/// <param name="y">
		/// A <see cref="float"/> indicating the translation on Y axis.
		/// </param>
		/// <param name="z">
		/// A <see cref="float"/> indicating the translation on Z axis.
		/// </param>
		public void Translate(float x, float y, float z)
		{
			base[3, 0] += x;
			base[3, 1] += y;
			base[3, 2] += z;
		}

		/// <summary>
		/// Translate this ModelMatrix.
		/// </summary>
		/// <param name="x">
		/// A <see cref="float"/> indicating the translation on X axis.
		/// </param>
		/// <param name="y">
		/// A <see cref="float"/> indicating the translation on Y axis.
		/// </param>
		public void Translate(float x, float y)
		{
			base[3, 0] += x;
			base[3, 1] += y;
		}

		/// <summary>
		/// Set translation to this ModelMatrix.
		/// </summary>
		/// <param name="x">
		/// A <see cref="float"/> indicating the translation on X axis.
		/// </param>
		/// <param name="y">
		/// A <see cref="float"/> indicating the translation on Y axis.
		/// </param>
		public void SetTranslate(float x, float y)
		{
			base[3, 0] = x;
			base[3, 1] = y;
		}

		/// <summary>
		/// Set translation to this ModelMatrix.
		/// </summary>
		/// <param name="x">
		/// A <see cref="float"/> indicating the translation on X axis.
		/// </param>
		/// <param name="y">
		/// A <see cref="float"/> indicating the translation on Y axis.
		/// </param>
		/// <param name="z">
		/// A <see cref="float"/> indicating the translation on Z axis.
		/// </param>
		public void SetTranslate(float x, float y, float z)
		{
			base[3, 0] = x;
			base[3, 1] = y;
			base[3, 2] = z;
		}

		/// <summary>
		/// Get translation of this ModelMatrix.
		/// </summary>
		/// <param name="p">
		/// A <see cref="Vertex3f"/> holding the this ModelMatrix translation.
		/// </param>
		public void GetTranslate(out Vertex3f p)
		{
			p.x = base[3, 0];
			p.y = base[3, 1];
			p.z = base[3, 2];
		}

		/// <summary>
		/// Get translation of this ModelMatrix.
		/// </summary>
		/// <param name="p">
		/// A <see cref="Vertex3f"/> holding the this ModelMatrix translation.
		/// </param>
		public void GetTranslate(out Vertex4f p)
		{
			p.x = base[3, 0];
			p.y = base[3, 1];
			p.z = base[3, 2];
			p.w = base[3, 3];
		}

		/// <summary>
		/// Get translation of this ModelMatrix.
		/// </summary>
		/// <param name="p">
		/// A <see cref="Vertex2f"/> holding the this ModelMatrix translation.
		/// </param>
		public void GetTranslate(Vertex2f p)
		{
			p.x = base[3, 0];
			p.y = base[3, 1];
		}

		/// <summary>
		/// Get translation of this ModelMatrix.
		/// </summary>
		/// <param name="x">
		/// A <see cref="System.Single"/> holding this ModelMatrix translation on X axis.
		/// </param>
		/// <param name="y">
		/// A <see cref="System.Single"/> holding this ModelMatrix translation on Y axis.
		/// </param>
		/// <param name="z">
		/// A <see cref="System.Single"/> holding this ModelMatrix translation on Z axis.
		/// </param>
		public void GetTranslate(out float x, out float y, out float z)
		{
			x = base[3, 0];
			y = base[3, 1];
			z = base[3, 2];
		}

		/// <summary>
		/// Get translation of this ModelMatrix.
		/// </summary>
		/// <param name="x">
		/// A <see cref="System.Single"/> holding this ModelMatrix translation on X axis.
		/// </param>
		/// <param name="y">
		/// A <see cref="System.Single"/> holding this ModelMatrix translation on Y axis.
		/// </param>
		public void GetTranslate(out float x, out float y)
		{
			x = base[3, 0];
			y = base[3, 1];
		}

		#endregion

		#region Rotation

		/// <summary>
		/// 
		/// </summary>
		/// <param name="angle"></param>
		public void SetRotateX(float angle)
		{
			SetIdentity();

			float cosa = (float) Math.Cos(angle * Math.PI / 180.0);
			float sina = (float) Math.Sin(angle * Math.PI / 180.0);

			this[1, 1] = +cosa;
			this[2, 1] = -sina;
			this[1, 2] = +sina;
			this[2, 2] = +cosa;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="angle"></param>
		public void SetRotateY(float angle)
		{
			SetIdentity();

			float cosa = (float) Math.Cos(angle * Math.PI / 180.0);
			float sina = (float) Math.Sin(angle * Math.PI / 180.0);

			this[0, 0] = +cosa;
			this[2, 0] = +sina;
			this[0, 2] = -sina;
			this[2, 2] = +cosa;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="angle"></param>
		public void SetRotateZ(float angle)
		{
			SetIdentity();

			float cosa = (float) Math.Cos(angle * Math.PI / 180.0);
			float sina = (float) Math.Sin(angle * Math.PI / 180.0);

			this[0, 0] = +cosa;
			this[1, 0] = -sina;
			this[0, 1] = +sina;
			this[1, 1] = +cosa;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="q"></param>
		public void SetRotate(Quaternion q)
		{
			SetIdentity();

			Set(this * (Matrix4x4)q);
		}

		/// <summary>
		/// Rotate this ModelMatrix
		/// </summary>
		/// <param name="q">
		/// A <see cref="Quaternion"/> representing the rotation.
		/// </param>
		public void Rotate(Quaternion q)
		{
			Set(this * (Matrix4x4)q);
		}

		/// <summary>
		/// Get a quaternion representing the rotation applied by this ModelMatrix.
		/// </summary>
		/// <param name="q">
		/// </param>
		public void GetRotate(out Quaternion q)
		{
			Vertex4f quaternionParams = new Vertex4f();

			// Formula 1
			// 
			// q4 = 0.5 * sqrt(1 + a11 + a22 + a33)
			// q1 = 1.0 / (4*q4) * (a32 - a23)
			// q2 = 1.0 / (4*q4) * (a13 - a31)
			// q3 = 1.0 / (4*q4) * (a21 - a12)

			// Formula 2
			// 
			// q1 = 0.5 * sqrt(1 + a11 - a22 - a33)
			// q2 = 1.0 / (4*q1) * (a12 + a21)
			// q3 = 1.0 / (4*q1) * (a13 + a31)
			// q4 = 1.0 / (4*q1) * (a32 - a23)

			quaternionParams.w = (float) (0.5 * Math.Sqrt(1.0f + this[0, 0] + this[1, 1] + this[2, 2]));
			quaternionParams.x = (1.0f / (4.0f * quaternionParams.w)) * (this[2, 1] - this[1, 2]);
			quaternionParams.y = (1.0f / (4.0f * quaternionParams.w)) * (this[0, 2] - this[2, 0]);
			quaternionParams.z = (1.0f / (4.0f * quaternionParams.w)) * (this[1, 0] - this[0, 1]);

			q = new Quaternion(quaternionParams.x, quaternionParams.y, quaternionParams.z, quaternionParams.w);
		}

		#endregion

		#region Scaling

		/// <summary>
		/// Scale this ModelMatrix.
		/// </summary>
		/// <param name="s">
		/// A <see cref="Vertex3f"/> holding the scaling factors on three dimensions.
		/// </param>
		public void SetScale(Vertex3f s)
		{
			SetScale(s.x, s.y, s.z);
		}

		/// <summary>
		/// Scale this ModelMatrix.
		/// </summary>
		/// <param name="s">
		/// A <see cref="Vertex3f"/> holding the scaling factors on three dimensions.
		/// </param>
		public void Scale(Vertex3f s)
		{
			ModelMatrix scaleModel = new ModelMatrix();
			scaleModel.SetScale(s);

			Set(this * scaleModel);
		}

		/// <summary>
		/// Scale this ModelMatrix.
		/// </summary>
		/// <param name="s">
		/// A <see cref="Vertex2f"/> holding the scaling factors on two dimensions.
		/// </param>
		public void SetScale(Vertex2f s)
		{
			SetScale(s.x, s.y, 1.0f);
		}

		/// <summary>
		/// Scale this ModelMatrix.
		/// </summary>
		/// <param name="x">
		/// A <see cref="System.Single"/> holding the scaling factor on X axis.
		/// </param>
		/// <param name="y">
		/// A <see cref="System.Single"/> holding the scaling factor on Y axis.
		/// </param>
		/// <param name="z">
		/// A <see cref="System.Single"/> holding the scaling factor on Z axis.
		/// </param>
		public void SetScale(float x, float y, float z)
		{
			Debug.Assert(Math.Abs(x) > Single.Epsilon);
			Debug.Assert(Math.Abs(y) > Single.Epsilon);
			Debug.Assert(Math.Abs(z) > Single.Epsilon);

			SetIdentity();

			base[0, 0] = x;
			base[1, 1] = y;
			base[2, 2] = z;
		}

		/// <summary>
		/// Scale this ModelMatrix.
		/// </summary>
		/// <param name="x">
		/// A <see cref="System.Single"/> holding the scaling factor on X axis.
		/// </param>
		/// <param name="y">
		/// A <see cref="System.Single"/> holding the scaling factor on Y axis.
		/// </param>
		public void SetScaleScale(float x, float y)
		{
			SetScale(x, y, 1.0f);
		}

		/// <summary>
		/// Scale this ModelMatrix.
		/// </summary>
		/// <param name="s">
		/// A <see cref="System.Single"/> holding the scaling factor on X, Y and Z axes.
		/// </param>
		public void SetScale(float s)
		{
			SetScale(s, s, s);
		}

		#endregion

		#endregion

		#region Operators

		/// <summary>
		/// Translate a ModelMatrix in three-dimensional space.
		/// </summary>
		/// <param name="m"></param>
		/// <param name="v"></param>
		/// <returns></returns>
		public static ModelMatrix operator +(ModelMatrix m, Vertex3f v)
		{
			ModelMatrix res = new ModelMatrix(m);

			res.Translate(v);

			return (res);
		}

		/// <summary>
		/// Translate a ModelMatrix in three-dimensional space.
		/// </summary>
		/// <param name="m"></param>
		/// <param name="v"></param>
		/// <returns></returns>
		public static ModelMatrix operator -(ModelMatrix m, Vertex3f v)
		{
			ModelMatrix res = new ModelMatrix(m);

			res.Translate(-v);

			return (res);
		}

		/// <summary>
		/// Translate a ModelMatrix in three-dimensional space.
		/// </summary>
		/// <param name="m"></param>
		/// <param name="v"></param>
		/// <returns></returns>
		public static ModelMatrix operator +(ModelMatrix m, Vertex2f v)
		{
			ModelMatrix res = new ModelMatrix(m);

			res.Translate(v);

			return (res);
		}

		/// <summary>
		/// Translate a ModelMatrix in two-dimensional space.
		/// </summary>
		/// <param name="m"></param>
		/// <param name="v"></param>
		/// <returns></returns>
		public static ModelMatrix operator -(ModelMatrix m, Vertex2f v)
		{
			ModelMatrix res = new ModelMatrix(m);

			res.Translate(-v);

			return (res);
		}

		/// <summary>
		/// Compute the product of two ModelMatrix.
		/// </summary>
		/// <param name="m1">
		/// A <see cref="Matrix4x4"/> that specifies the left multiplication operand.
		/// </param>
		/// <param name="m2">
		/// A <see cref="Matrix4x4"/> that specifies the right multiplication operand.
		/// </param>
		/// <returns>
		/// A <see cref="Matrix4x4"/> resulting from the product of the matrix <paramref name="m1"/> and
		/// the matrix <paramref name="m2"/>. This operator is used to concatenate successive transformations.
		/// </returns>
		public static ModelMatrix operator *(ModelMatrix m1, ModelMatrix m2)
		{
			// Allocate product matrix
			ModelMatrix prod = new ModelMatrix();

			// Compute product
			ComputeMatrixProduct(prod, m1, m2);

			return (prod);
		}

		#endregion

		#region Identity

		/// <summary>
		/// The identity matrix.
		/// </summary>
		public static readonly ModelMatrix Identity = new ModelMatrix();

		#endregion

		#region Matrix4x4 Overrides

		/// <summary>
		/// Clone this ModelMatrix.
		/// </summary>
		/// <returns>
		/// It returns a deep copy of this ModelMatrix.
		/// </returns>
		public override Matrix Clone()
		{
			return (new ModelMatrix(this));
		}

		/// <summary>
		/// Generates an object from its XML representation.
		/// </summary>
		/// <param name="reader">
		/// The System.Xml.XmlReader stream from which the object is deserialized.
		/// </param>
		public override void ReadXml(XmlReader reader)
		{

		}

		/// <summary>
		/// Converts an object into its XML representation.
		/// </summary>
		/// <param name="writer">
		/// The System.Xml.XmlWriter stream to which the object is serialized.
		/// </param>
		public override void WriteXml(XmlWriter writer)
		{
			Vertex3f position;

			// Get matrix information
			GetTranslate(out position);

			// Element: rotation
			writer.WriteStartAttribute(sXmlRotationElement);

			writer.WriteEndElement();

			// Element: position
			writer.WriteStartAttribute(sXmlPositionElement);
			position.WriteXml(writer);
			writer.WriteEndElement();

			// Example of output:
			//
			// <ModelMatrix>
			//   <Position X="0.0" Y="0.0" Z="0.0"/>
			//   <Rotation X="0.0" Y="0.0" Z="0.0" Angle="0.0">
			// </ModelMatrix>
		}

		/// <summary>
		/// Name of the element that specify the matrix column.
		/// </summary>
		private static readonly string sXmlPositionElement = "Position";

		/// <summary>
		/// Name of the element that specify the matrix column values.
		/// </summary>
		private static readonly string sXmlRotationElement = "Rotation";

		#endregion

		#region Object Overrides

		/// <summary>
		/// Stringify this Vertex3f.
		/// </summary>
		/// <returns>
		/// Returns a <see cref="System.String"/> that represents this Vertex3f.
		/// </returns>
		public override string ToString()
		{
			Vertex3f translate;
			Quaternion rotate;

			GetTranslate(out translate);
			GetRotate(out rotate);

			return (String.Format("ModelMatrix: Position={0}, Rotation={1}", translate.ToString(), rotate.ToString()));
		}

		#endregion
	}
}
